﻿using System.Collections.Generic;
using System.Xml;
using System.Linq;
using BakalarskaPraca.Forms;
using BakalarskaPraca.Generation.Data;
using BakalarskaPraca.Generation.Helpers;
using BakalarskaPraca.Generation.Tasks.Abstract;
using ColladaDotNet.Geometry;
using ColladaDotNet.Scene;
using Microsoft.DirectX;

namespace BakalarskaPraca.Generation.Tasks
{
    public class SceneLoader : WorkTask
    {
        private Dictionary<string, Geometry> _geometrySources;
        private Dictionary<string, int> _geometryCopiesIdentifier; 

        public SceneLoader(ReportProgress reporter, string taskName) : base(reporter, taskName)
        {
            this._geometrySources = new Dictionary<string, Geometry>();
            this._geometryCopiesIdentifier = new Dictionary<string, int>();
        }

        public List<ModelGeometry> LoadScene(VisualScene scene, Dictionary<string, Geometry> geometries)
        {
            int current = 0;
            int size = scene.Nodes.Count;
            this._geometrySources = geometries;
            List<Geometry> result = new List<Geometry>();
            scene.Nodes.ForEach(node =>
                                    {
                                        result.AddRange(this.LoadNode(node));
                                        this.ReportProgressState(current++, size);
                                    });
            return result.Select(g => new ModelGeometry(g)).ToList();
        }

        private List<Geometry> LoadNode(Node node)
        {
            List<Geometry> result = new List<Geometry>();

            List<Node> realNodes = new List<Node>();
            realNodes.AddRange(node.Nodes);
            realNodes.AddRange(this.GetLinkedNodeInstances(node));
            
            realNodes.ForEach(n =>
                {
                    var list = this.LoadNode(n);
                    if (list != null)
                    {
                        result.AddRange(list);
                    }
                });

            Matrix transformationMatrix = Matrix.Identity;

            foreach (XmlNode xmln in node.XmlElement.ChildNodes)
            {
                if (xmln.Name.Equals("instance_geometry"))
                {
                    if (xmln.Attributes != null)
                    {
                        string id = xmln.Attributes["url"].Value;
                        if (id.StartsWith("#"))
                        {
                            id = id.Substring(1);
                            Geometry g;
                            if (this._geometrySources.TryGetValue(id, out g))
                            {
                                g = GeometryHelper.GeometryCopy(g);
                                g.Id = this.GetUniqueName(g.Id);
                                result.Add(g);
                            }
                        }
                    }
                }
                if (xmln.Name.Equals("matrix"))
                {
                    transformationMatrix = MatrixHelper.LoadFromString(xmln.InnerText);
                }
            }

            transformationMatrix = Matrix.Multiply(node.GetWorldTransformationMatrix(), transformationMatrix);

            result.ForEach(
                g => TransformGeometry(g, transformationMatrix)
            );

            return result;
        }

        private IEnumerable<Node> GetLinkedNodeInstances(Node node)
        {
            List<Node> result = new List<Node>();
            foreach (XmlNode xmln in node.XmlElement.ChildNodes)
            {
                if (xmln.Name == "instance_node")
                {
                    if (xmln.Attributes != null)
                    {
                        string id = xmln.Attributes["url"].Value;
                        if (id.StartsWith("#"))
                        {
                            id = id.Substring(1);
                            string select = "//*[@id='" + id + "']";
                            XmlElement elem = node.XmlElement.SelectSingleNode(@select) as XmlElement;
                            if (elem != null)
                            {
                                result.Add(new Node(elem, node));
                            }
                        }
                    }
                }
            }
            return result;
        }

        private static void TransformGeometry(Geometry g, Matrix transformationMatrix)
        {
            g.Vertices.ForEach(
                v => v.Positions.ForEach(
                    p =>
                    {
                        Vector4 vector = Vector3.Transform(p.Vector, transformationMatrix);
                        p.X = vector.X;
                        p.Y = vector.Y;
                        p.Z = vector.Z;
                    }
                )
            );
        }

        private string GetUniqueName(string id)
        {
            int identifier;
            if (this._geometryCopiesIdentifier.TryGetValue(id, out identifier))
            {
                this._geometryCopiesIdentifier[id] = identifier + 1;
                return id + "_copy(" + identifier + ")";
            }
            this._geometryCopiesIdentifier.Add(id, 0);
            return id;
        }

    }
}
